package csel.model;

import java.util.*;

import org.apache.log4j.Logger;

import csel.model.areaeffect.AreaEffect;
import csel.model.entity.Entity;
import csel.model.items.Item;

public class RealTile implements Tile, java.io.Serializable
{
	private static final long serialVersionUID = 8L;
	
	/* LOGGING */
	private static final Logger logger = Logger.getLogger("csel.model");
	
	private volatile Entity entity;
	private Item item;
	private Decal decal;
	private Location location;
	private Terrain terrain;
	private AreaEffect areaEffect;
	private final Map map;
	private final ArrayList<Projectile> projectiles;

	public RealTile(Map map)
	{
		this.map = map;
		this.projectiles = new ArrayList<Projectile>();
	}
	
	public AreaEffect getAreaEffect()
	{
		return areaEffect;
	}

	public void setAreaEffect(AreaEffect areaEffect)
	{
		this.areaEffect = areaEffect;
	}

	public Decal getDecal()
	{
		return decal;
	}

	public void setDecal(Decal decal)
	{
		this.decal = decal;
	}

	public Entity getEntity()
	{
		return entity;
	}

	public Item getItem()
	{
		return item;
	}

	public void setItem(Item item)
	{
		this.item = item;
		
		if (item != null)
			item.setLocation(this);
	}

	public Location getLocation()
	{
		return location;
	}

	public void setLocation(Location location)
	{
		this.location = location;
	}

	public Tile getNeighbor(Direction direction)
	{
		return map.getTile(location.getX() + direction.getX(), location.getY() + direction.getY());
	}

	public Terrain getTerrain()
	{
		return terrain;
	}

	public void setTerrain(Terrain terrain)
	{
		this.terrain = terrain;
	}

	// tests for item is impassable, terrain can pass and if entity is in tile
	public boolean canEnter(MobileObject obj, BlockNotifier notifier)
	{
		// TODO Should this consider entity if it is used for diagonal movements?
		
		if (terrain != null && !terrain.canEnter(obj, notifier))
		{
			logger.debug(String.format("%s cannot pass terrain %s.", obj, terrain));
			return false;
		}
		
		if (item != null && !item.canEnter(obj, notifier))
			return false;
		
		if (entity != null && !entity.canEnter(obj, notifier))
			return false;
		
		// TODO handle bumping
		
		return true;
	}

	public void enter(Entity e)
	{
		if (!this.canEnter(e, null))
			throw new NoCanDoException("Can't move here!");
		
		if (entity != null)
		{
			e.touch(entity);
		} else
		{
			entity = e;
		}

		/* Note below that second condition won't be executed if first fails. */
		if (item != null && item.enter(e))
		{
			/* item wants to be removed */
			item = null;
		}
		
		if (areaEffect != null)
			areaEffect.enter(e);
	}

	public boolean canLeave(MobileObject obj)
	{
		if (item != null && !item.canLeave(obj))
			return false;
		
		return true;
	}

	public void leave(Entity e)
	{
		if (!this.canLeave(e))
			throw new NoCanDoException("Can't leave here!");
		
		entity = null;
		
		if (item != null)
			item.leave(e);
		
		if (areaEffect != null)
			areaEffect.leave(e);
	}

	public void accept(TileVisitor tv)
	{
		Item i;
		Decal d;
		AreaEffect ae;
		Entity e;
		
		tv.visitTerrain(this.getTerrain());
		
		
		if ((d = this.getDecal()) != null)
			tv.visitDecal(d);
		
		if ((i = this.getItem()) != null)
			tv.visitItem(i);
		
		if ((ae = this.getAreaEffect()) != null)
			tv.visitAreaEffect(ae);
		
		if ((e = this.getEntity()) != null)
			tv.visitEntity(e);

        for (Projectile p : projectiles)
                tv.visitProjectile(p);
	}

	public void enter(Projectile p)
	{
		projectiles.add(p);

	}
	
	public Map getMap()
	{
		return (map);
	}

	public List<Projectile> getProjectiles()
	{
		return (projectiles);
	}

	public void leave(Projectile p)
	{
		projectiles.remove(p);

	}
	
	public String toString()
	{
		return String.format("Tile@%s", location.toString());
	}

	@Override
	public void clearEntity()
	{
		this.entity = null;
		
	}
}
